#![allow(non_snake_case)]
use leptos::{ev, html, prelude::*};
use std::{fmt::Display, hash::Hash};
use thaw_components::{Follower, FollowerPlacement, FollowerWidth};
use thaw_utils::{ArcOneCallback, Model};

use crate::{SelectOption, _binder::Binder};

#[component]
pub fn SelectRaw<T>(
    #[prop(into)] values: Model<Vec<T>>,
    #[prop(optional, into)] options: MaybeProp<Vec<SelectOption<T>>>,
    #[prop(into)] on_select: ArcOneCallback<Option<T>>,
    #[prop(optional, into)] can_filter: MaybeProp<bool>,
    #[prop(optional, into)] is_show_menu: RwSignal<bool>,
    children: Children,
) -> impl IntoView
where
    T: Eq + Hash + Clone + Ord + Display + Send + Sync + 'static,
{
    let filter_val: RwSignal<Option<String>> = RwSignal::new(None);
    let sel_idx: RwSignal<usize> = RwSignal::new(0);
    let trigger_ref = NodeRef::<html::Div>::new();
    let menu_ref = NodeRef::<html::Div>::new();
    let filter_ref = NodeRef::<html::Input>::new();
    Effect::new(move |_| {
        if !is_show_menu.get() {
            return;
        }
        if can_filter.get_untracked().unwrap_or_default() {
            filter_ref.get_untracked().map(|el| {
                _ = el.focus();
            });
        }
    });
    let filter_options = Memo::new(move |_| {
        let fv = filter_val.get();
        let options: Vec<SelectOption<T>> = options.get_untracked().unwrap_or_default();
        if let Some(fv) = fv {
            let opts: Vec<SelectOption<T>> = options
                .into_iter()
                .filter(|o| o.label.contains(&fv))
                .collect();
            opts
        } else {
            options
        }
    });
    #[cfg(any(feature = "csr", feature = "hydrate"))]
    {
        use leptos::wasm_bindgen::__rt::IntoJsResult;
        let timer = window_event_listener(ev::click, move |ev| {
            let Some(menu_ref) = menu_ref.get_untracked() else {
                return;
            };
            let Some(trigger_ref) = trigger_ref.get_untracked() else {
                return;
            };
            let el = ev.target();
            let mut el: Option<web_sys::Element> =
                el.into_js_result().map_or(None, |el| Some(el.into()));
            let body = document().body().unwrap();
            while let Some(current_el) = el {
                if current_el == *body {
                    break;
                };
                if current_el == **menu_ref || current_el == **trigger_ref {
                    return;
                }
                el = current_el.parent_element();
            }
            is_show_menu.set(false);
        });
        let on_select = on_select.clone();
        let on_keydown = move |e: ev::KeyboardEvent| {
            let key = e.key();
            if key == *"Enter" || key == *"Escape" || key == *"ArrowUp" || key == *"ArrowDown" {
                e.prevent_default();
                e.stop_propagation();
            }
            let idx = sel_idx.get();
            if key == *"Enter" {
                filter_options.with(|vs| {
                    let val = vs[idx].value.clone();
                    on_select(Some(val));
                    is_show_menu.set(false);
                });
                return;
            } else if key == *"Escape" {
                is_show_menu.set(false);
                return;
            }
            let mut cur_idx = sel_idx.get() as isize;
            if key == *"ArrowUp" {
                cur_idx = cur_idx - 1;
            } else if key == *"ArrowDown" {
                cur_idx = cur_idx + 1;
            }
            if cur_idx < 0 {
                return;
            }
            filter_options.with(|vs| {
                let count = vs.len();
                let cur_idx2 = cur_idx as usize;
                if cur_idx2 < count {
                    sel_idx.set(cur_idx2);
                }
            })
        };
        let listener = window_event_listener(ev::keydown, move |ev| {
            if !is_show_menu.get() {
                return;
            }
            on_keydown(ev)
        });
        on_cleanup(move || {
            timer.remove();
            listener.remove();
        });
    }
    let on_mousedown = move |ev: ev::MouseEvent| {
        ev.prevent_default();
        if !is_show_menu.get() {
            is_show_menu.set(true);
        }
    };
    view! {
        <Binder>
            <div class="max-w-xs m-0 p-0"
                node_ref=trigger_ref
                on:mousedown=on_mousedown>
                {children()}
            </div>
            <Follower
                slot
                show=is_show_menu
                placement=FollowerPlacement::BottomStart
                width=FollowerWidth::Target
            >
                <div class="shadow bg-base-100 rounded-box z-10 max-h-72 overflow-auto"
                    node_ref=menu_ref class=("hidden",move||!is_show_menu.get())>
                    <div class="p-2" class=("hidden",move||!can_filter.get().unwrap_or_default())>
                        <input type="text" placeholder="筛选" node_ref=filter_ref
                            class="input input-bordered input-sm w-full max-w-xs"
                            on:input=move |ev| {
                                let val = event_target_value(&ev).trim().to_string();
                                if val.is_empty() {
                                    filter_val.set(None);
                                }else{
                                    filter_val.set(Some(val.trim().to_string()));
                                }
                                sel_idx.set(0);
                        }
                        />
                    </div>
                    <ul class="menu">
                        <For
                            each=move ||filter_options.get().into_iter().enumerate()
                            key=move |(i,item)| (i.clone(),item.value.clone())
                            children=move |(i,item)| {
                                let on_select = on_select.clone();
                                let val=StoredValue::new(item.value.clone());
                                let lab=item.label.clone();
                                let onclick = move |_| {
                                    on_select(Some(val.get_value()));
                                };
                                view! {
                                    <li
                                    class="menu-item"
                                        on:click=onclick
                                        class=("bg-base-300",move || sel_idx.get()==i)
                                        class=("rounded-lg",move || sel_idx.get()==i)
                                    >
                                        <a class=("active",move || values.with(|vs| vs.iter().any(|v| *v == item.value)))
                                        >
                                            {lab}
                                        </a>
                                    </li>
                                }
                            }
                        />
                    </ul>
                </div>
            </Follower>
        </Binder>
    }
}
